/*! @preserve
 * bootbox.js
 * version: 5.1.1
 * author: Nick Payne <nick@kurai.co.uk>
 * license: MIT
 * http://bootboxjs.com/
 */
(function (root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
      // AMD
      define(['jquery'], factory);
    } else if (typeof exports === 'object') {
      // Node, CommonJS-like
      module.exports = factory(require('jquery'));
    } else {
      // Browser globals (root is window)
      root.bootbox = factory(root.jQuery);
    }
  }(this, function init($, undefined) {
    'use strict';
  
    //  Polyfills Object.keys, if necessary.
    //  @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
    if (!Object.keys) {
      Object.keys = (function () {
        var hasOwnProperty = Object.prototype.hasOwnProperty,
          hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
          dontEnums = [
            'toString',
            'toLocaleString',
            'valueOf',
            'hasOwnProperty',
            'isPrototypeOf',
            'propertyIsEnumerable',
            'constructor'
          ],
          dontEnumsLength = dontEnums.length;
  
        return function (obj) {
          if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
            throw new TypeError('Object.keys called on non-object');
          }
  
          var result = [], prop, i;
  
          for (prop in obj) {
            if (hasOwnProperty.call(obj, prop)) {
              result.push(prop);
            }
          }
  
          if (hasDontEnumBug) {
            for (i = 0; i < dontEnumsLength; i++) {
              if (hasOwnProperty.call(obj, dontEnums[i])) {
                result.push(dontEnums[i]);
              }
            }
          }
  
          return result;
        };
      }());
    }
  
    var exports = {};
  
    var VERSION = '5.0.0';
    exports.VERSION = VERSION;
  
    var locales = {
        ar : {
          OK      : "موافق",
          CANCEL  : "الغاء",
          CONFIRM : "تأكيد"
        },
        bg_BG : {
          OK      : "Ок",
          CANCEL  : "Отказ",
          CONFIRM : "Потвърждавам"
        },
        br : {
          OK      : "OK",
          CANCEL  : "Cancelar",
          CONFIRM : "Sim"
        },
        cs : {
          OK      : "OK",
          CANCEL  : "Zrušit",
          CONFIRM : "Potvrdit"
        },
        da : {
          OK      : "OK",
          CANCEL  : "Annuller",
          CONFIRM : "Accepter"
        },
        de : {
          OK      : "OK",
          CANCEL  : "Abbrechen",
          CONFIRM : "Akzeptieren"
        },
        el : {
          OK      : "Εντάξει",
          CANCEL  : "Ακύρωση",
          CONFIRM : "Επιβεβαίωση"
        },
        en : {
          OK      : "OK",
          CANCEL  : "Cancel",
          CONFIRM : "OK"
        },
        es : {
          OK      : "OK",
          CANCEL  : "Cancelar",
          CONFIRM : "Aceptar"
        },
        eu : {
          OK      : "OK",
          CANCEL  : "Ezeztatu",
          CONFIRM : "Onartu"
        },
        et : {
          OK      : "OK",
          CANCEL  : "Katkesta",
          CONFIRM : "OK"
        },
        fa : {
          OK      : "قبول",
          CANCEL  : "لغو",
          CONFIRM : "تایید"
        },
        fi : {
          OK      : "OK",
          CANCEL  : "Peruuta",
          CONFIRM : "OK"
        },
        fr : {
          OK      : "OK",
          CANCEL  : "Annuler",
          CONFIRM : "Confirmer"
        },
        he : {
          OK      : "אישור",
          CANCEL  : "ביטול",
          CONFIRM : "אישור"
        },
        hu : {
          OK      : "OK",
          CANCEL  : "Mégsem",
          CONFIRM : "Megerősít"
        },
        hr : {
          OK      : "OK",
          CANCEL  : "Odustani",
          CONFIRM : "Potvrdi"
        },
        id : {
          OK      : "OK",
          CANCEL  : "Batal",
          CONFIRM : "OK"
        },
        it : {
          OK      : "OK",
          CANCEL  : "Annulla",
          CONFIRM : "Conferma"
        },
        ja : {
          OK      : "OK",
          CANCEL  : "キャンセル",
          CONFIRM : "確認"
        },
        lt : {
          OK      : "Gerai",
          CANCEL  : "Atšaukti",
          CONFIRM : "Patvirtinti"
        },
        lv : {
          OK      : "Labi",
          CANCEL  : "Atcelt",
          CONFIRM : "Apstiprināt"
        },
        nl : {
          OK      : "OK",
          CANCEL  : "Annuleren",
          CONFIRM : "Accepteren"
        },
        no : {
          OK      : "OK",
          CANCEL  : "Avbryt",
          CONFIRM : "OK"
        },
        pl : {
          OK      : "OK",
          CANCEL  : "Anuluj",
          CONFIRM : "Potwierdź"
        },
        pt : {
          OK      : "OK",
          CANCEL  : "Cancelar",
          CONFIRM : "Confirmar"
        },
        ru : {
          OK      : "OK",
          CANCEL  : "Отмена",
          CONFIRM : "Применить"
        },
        sk : {
          OK      : "OK",
          CANCEL  : "Zrušiť",
          CONFIRM : "Potvrdiť"
        },
        sl : {
          OK      : "OK",
          CANCEL  : "Prekliči",
          CONFIRM : "Potrdi"
        },
        sq : {
          OK      : "OK",
          CANCEL  : "Anulo",
          CONFIRM : "Prano"
        },
        sv : {
          OK      : "OK",
          CANCEL  : "Avbryt",
          CONFIRM : "OK"
        },
        sw: {
          OK      : 'Sawa',
          CANCEL  : 'Ghairi',
          CONFIRM: 'Thibitisha'
        },
        ta:{
          OK      : 'சரி',
          CANCEL  : 'ரத்து செய்',
          CONFIRM : 'உறுதி செய்'
        },
        th : {
          OK      : "ตกลง",
          CANCEL  : "ยกเลิก",
          CONFIRM : "ยืนยัน"
        },
        tr : {
          OK      : "Tamam",
          CANCEL  : "İptal",
          CONFIRM : "Onayla"
        },
        uk : {
          OK      : "OK",
          CANCEL  : "Відміна",
          CONFIRM : "Прийняти"
        },
        zh_CN : {
          OK      : "OK",
          CANCEL  : "取消",
          CONFIRM : "确认"
        },
        zh_TW : {
          OK      : "OK",
          CANCEL  : "取消",
          CONFIRM : "確認"
        }
    };
  
    var templates = {
      dialog:
      '<div class="bootbox modal" tabindex="-1" role="dialog" aria-hidden="true">' +
      '<div class="modal-dialog">' +
      '<div class="modal-content">' +
      '<div class="modal-body"><div class="bootbox-body"></div></div>' +
      '</div>' +
      '</div>' +
      '</div>',
      header:
      '<div class="modal-header">' +
      '<h5 class="modal-title"></h5>' +
      '</div>',
      footer:
      '<div class="modal-footer"></div>',
      closeButton:
      '<button type="button" class="bootbox-close-button close" aria-hidden="true">&times;</button>',
      form:
      '<form class="bootbox-form"></form>',
      button:
      '<button type="button" class="btn"></button>',
      option:
      '<option></option>',
      promptMessage:
      '<div class="bootbox-prompt-message"></div>',
      inputs: {
        text:
        '<input class="bootbox-input bootbox-input-text form-control" autocomplete="off" type="text" />',
        textarea:
        '<textarea class="bootbox-input bootbox-input-textarea form-control"></textarea>',
        email:
        '<input class="bootbox-input bootbox-input-email form-control" autocomplete="off" type="email" />',
        select:
        '<select class="bootbox-input bootbox-input-select form-control"></select>',
        checkbox:
        '<div class="form-check checkbox"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-checkbox" type="checkbox" /></label></div>',
        radio:
        '<div class="form-check radio"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-radio" type="radio" name="bootbox-radio" /></label></div>',
        date:
        '<input class="bootbox-input bootbox-input-date form-control" autocomplete="off" type="date" />',
        time:
        '<input class="bootbox-input bootbox-input-time form-control" autocomplete="off" type="time" />',
        number:
        '<input class="bootbox-input bootbox-input-number form-control" autocomplete="off" type="number" />',
        password:
        '<input class="bootbox-input bootbox-input-password form-control" autocomplete="off" type="password" />',
        range:
        '<input class="bootbox-input bootbox-input-range form-control-range" autocomplete="off" type="range" />'
      }
    };
  
  
    var defaults = {
      // default language
      locale: 'en',
      // show backdrop or not. Default to static so user has to interact with dialog
      backdrop: 'static',
      // animate the modal in/out
      animate: true,
      // additional class string applied to the top level dialog
      className: null,
      // whether or not to include a close button
      closeButton: true,
      // show the dialog immediately by default
      show: true,
      // dialog container
      container: 'body',
      // default value (used by the prompt helper)
      value: '',
      // default input type (used by the prompt helper)
      inputType: 'text',
      // switch button order from cancel/confirm (default) to confirm/cancel
      swapButtonOrder: false,
      // center modal vertically in page
      centerVertical: false,
      // Append "multiple" property to the select when using the "prompt" helper
      multiple: false,
      // Automatically scroll modal content when height exceeds viewport height
      scrollable: false
    };
  
  
    // PUBLIC FUNCTIONS
    // *************************************************************************************************************
  
    // Return all currently registered locales, or a specific locale if "name" is defined
    exports.locales = function (name) {
      return name ? locales[name] : locales;
    };
  
  
    // Register localized strings for the OK, Confirm, and Cancel buttons
    exports.addLocale = function (name, values) {
      $.each(['OK', 'CANCEL', 'CONFIRM'], function (_, v) {
        if (!values[v]) {
          throw new Error('Please supply a translation for "' + v + '"');
        }
      });
  
      locales[name] = {
        OK: values.OK,
        CANCEL: values.CANCEL,
        CONFIRM: values.CONFIRM
      };
  
      return exports;
    };
  
  
    // Remove a previously-registered locale
    exports.removeLocale = function (name) {
      if (name !== 'en') {
        delete locales[name];
      }
      else {
        throw new Error('"en" is used as the default and fallback locale and cannot be removed.');
      }
  
      return exports;
    };
  
  
    // Set the default locale
    exports.setLocale = function (name) {
      return exports.setDefaults('locale', name);
    };
  
  
    // Override default value(s) of Bootbox.
    exports.setDefaults = function () {
      var values = {};
  
      if (arguments.length === 2) {
        // allow passing of single key/value...
        values[arguments[0]] = arguments[1];
      } else {
        // ... and as an object too
        values = arguments[0];
      }
  
      $.extend(defaults, values);
  
      return exports;
    };
  
  
    // Hides all currently active Bootbox modals
    exports.hideAll = function () {
      $('.bootbox').modal('hide');
  
      return exports;
    };
  
  
    // Allows the base init() function to be overridden
    exports.init = function (_$) {
      return init(_$ || $);
    };
  
  
    // CORE HELPER FUNCTIONS
    // *************************************************************************************************************
  
    // Core dialog function
    exports.dialog = function (options) {
      if ($.fn.modal === undefined) {
        throw new Error(
          '"$.fn.modal" is not defined; please double check you have included ' +
          'the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ ' +
          'for more details.'
        );
      }
  
      options = sanitize(options);
  
      if ($.fn.modal.Constructor.VERSION) {
        options.fullBootstrapVersion = $.fn.modal.Constructor.VERSION;
        var i = options.fullBootstrapVersion.indexOf('.');
        options.bootstrap = options.fullBootstrapVersion.substring(0, i);
      }
      else {
        // Assuming version 2.3.2, as that was the last "supported" 2.x version
        options.bootstrap = '2';
        options.fullBootstrapVersion = '2.3.2';
        console.warn('Bootbox will *mostly* work with Bootstrap 2, but we do not officially support it. Please upgrade, if possible.');
      }
  
      var dialog = $(templates.dialog);
      var innerDialog = dialog.find('.modal-dialog');
      var body = dialog.find('.modal-body');
      var header = $(templates.header);
      var footer = $(templates.footer);
      var buttons = options.buttons;
  
      var callbacks = {
        onEscape: options.onEscape
      };
  
      body.find('.bootbox-body').html(options.message);
  
      // Only attempt to create buttons if at least one has 
      // been defined in the options object
      if (getKeyLength(options.buttons) > 0) {
        each(buttons, function (key, b) {
          var button = $(templates.button);
          button.data('bb-handler', key);
          button.addClass(b.className);
  
          switch(key)
          {
            case 'ok':
            case 'confirm':
              button.addClass('bootbox-accept');
              break;
  
            case 'cancel':
              button.addClass('bootbox-cancel');
              break;
          }
  
          button.html(b.label);
          footer.append(button);
  
          callbacks[key] = b.callback;
        });
  
        body.after(footer);
      }
  
      if (options.animate === true) {
        dialog.addClass('fade');
      }
  
      if (options.className) {
        dialog.addClass(options.className);
      }
  
      if (options.size) {
        // Requires Bootstrap 3.1.0 or higher
        if (options.fullBootstrapVersion.substring(0, 3) < '3.1') {
          console.warn('"size" requires Bootstrap 3.1.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
        }
  
        switch(options.size)
        {
          case 'small':
          case 'sm':
            innerDialog.addClass('modal-sm');
            break;
  
          case 'large':
          case 'lg':
            innerDialog.addClass('modal-lg');
            break;
  
          case 'xl':
          case 'extra-large':
            // Requires Bootstrap 4.2.0 or higher
            if (options.fullBootstrapVersion.substring(0, 3) < '4.2') {
              console.warn('Using size "xl"/"extra-large" requires Bootstrap 4.2.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
            }
            innerDialog.addClass('modal-xl');
            break;
        }
      }
  
      if(options.scrollable){
        // Requires Bootstrap 4.3.0 or higher
        if (options.fullBootstrapVersion.substring(0, 3) < '4.3') {
          console.warn('Using "scrollable" requires Bootstrap 4.3.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
        }
  
        innerDialog.addClass('modal-dialog-scrollable');
      }
  
      if (options.title) {
        body.before(header);
        dialog.find('.modal-title').html(options.title);
      }
  
      if (options.closeButton) {
        var closeButton = $(templates.closeButton);
  
        if (options.title) {
          if (options.bootstrap > 3) {
            dialog.find('.modal-header').append(closeButton);
          }
          else {
            dialog.find('.modal-header').prepend(closeButton);
          }
        } else {
          closeButton.prependTo(body);
        }
      }
  
      if(options.centerVertical){
        // Requires Bootstrap 4.0.0-beta.3 or higher
        if (options.fullBootstrapVersion < '4.0.0') {
          console.warn('"centerVertical" requires Bootstrap 4.0.0-beta.3 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
        }
  
        innerDialog.addClass('modal-dialog-centered');
      }
  
      // Bootstrap event listeners; these handle extra
      // setup & teardown required after the underlying
      // modal has performed certain actions.
  
      // make sure we unbind any listeners once the dialog has definitively been dismissed
        dialog.one('hide.bs.modal', function (e) {
          if (e.target === this) {
            dialog.off('escape.close.bb');
            dialog.off('click');
          }
      });
  
      dialog.one('hidden.bs.modal', function (e) {
        // ensure we don't accidentally intercept hidden events triggered
        // by children of the current dialog. We shouldn't need to handle this anymore, 
        // now that Bootstrap namespaces its events, but still worth doing.
        if (e.target === this) {
          dialog.remove();
        }
      });
  
      dialog.one('shown.bs.modal', function () {
        dialog.find('.bootbox-accept:first').trigger('focus');
      });
  
      // Bootbox event listeners; used to decouple some
      // behaviours from their respective triggers
  
      if (options.backdrop !== 'static') {
        // A boolean true/false according to the Bootstrap docs
        // should show a dialog the user can dismiss by clicking on
        // the background.
        // We always only ever pass static/false to the actual
        // $.modal function because with "true" we can't trap
        // this event (the .modal-backdrop swallows it)
        // However, we still want to sort of respect true
        // and invoke the escape mechanism instead
        dialog.on('click.dismiss.bs.modal', function (e) {
          // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
          // moved *inside* the outer dialog rather than *alongside* it
          if (dialog.children('.modal-backdrop').length) {
            e.currentTarget = dialog.children('.modal-backdrop').get(0);
          }
  
          if (e.target !== e.currentTarget) {
            return;
          }
  
          dialog.trigger('escape.close.bb');
        });
      }
  
      dialog.on('escape.close.bb', function (e) {
        // the if statement looks redundant but it isn't; without it
        // if we *didn't* have an onEscape handler then processCallback
        // would automatically dismiss the dialog
        if (callbacks.onEscape) {
          processCallback(e, dialog, callbacks.onEscape);
        }
      });
  
  
      dialog.on('click', '.modal-footer button:not(.disabled)', function (e) {
        var callbackKey = $(this).data('bb-handler');
  
        processCallback(e, dialog, callbacks[callbackKey]);
      });
  
      dialog.on('click', '.bootbox-close-button', function (e) {
        // onEscape might be falsy but that's fine; the fact is
        // if the user has managed to click the close button we
        // have to close the dialog, callback or not
        processCallback(e, dialog, callbacks.onEscape);
      });
  
      dialog.on('keyup', function (e) {
        if (e.which === 27) {
          dialog.trigger('escape.close.bb');
        }
      });
  
      // the remainder of this method simply deals with adding our
      // dialogent to the DOM, augmenting it with Bootstrap's modal
      // functionality and then giving the resulting object back
      // to our caller
  
      $(options.container).append(dialog);
  
      dialog.modal({
        backdrop: options.backdrop ? 'static' : false,
        keyboard: false,
        show: false
      });
  
      if (options.show) {
        dialog.modal('show');
      }
  
      return dialog;
    };
  
  
    // Helper function to simulate the native alert() behavior. **NOTE**: This is non-blocking, so any
    // code that must happen after the alert is dismissed should be placed within the callback function 
    // for this alert.
    exports.alert = function () {
      var options;
  
      options = mergeDialogOptions('alert', ['ok'], ['message', 'callback'], arguments);
  
      // @TODO: can this move inside exports.dialog when we're iterating over each
      // button and checking its button.callback value instead?
      if (options.callback && !$.isFunction(options.callback)) {
        throw new Error('alert requires the "callback" property to be a function when provided');
      }
  
      // override the ok and escape callback to make sure they just invoke
      // the single user-supplied one (if provided)
      options.buttons.ok.callback = options.onEscape = function () {
        if ($.isFunction(options.callback)) {
          return options.callback.call(this);
        }
  
        return true;
      };
  
      return exports.dialog(options);
    };
  
  
    // Helper function to simulate the native confirm() behavior. **NOTE**: This is non-blocking, so any
    // code that must happen after the confirm is dismissed should be placed within the callback function 
    // for this confirm.
    exports.confirm = function () {
      var options;
  
      options = mergeDialogOptions('confirm', ['cancel', 'confirm'], ['message', 'callback'], arguments);
  
      // confirm specific validation; they don't make sense without a callback so make
      // sure it's present
      if (!$.isFunction(options.callback)) {
        throw new Error('confirm requires a callback');
      }
  
      // overrides; undo anything the user tried to set they shouldn't have
      options.buttons.cancel.callback = options.onEscape = function () {
        return options.callback.call(this, false);
      };
  
      options.buttons.confirm.callback = function () {
        return options.callback.call(this, true);
      };
  
      return exports.dialog(options);
    };
  
  
    // Helper function to simulate the native prompt() behavior. **NOTE**: This is non-blocking, so any
    // code that must happen after the prompt is dismissed should be placed within the callback function 
    // for this prompt.
    exports.prompt = function () {
      var options;
      var promptDialog;
      var form;
      var input;
      var shouldShow;
      var inputOptions;
  
      // we have to create our form first otherwise
      // its value is undefined when gearing up our options
      // @TODO this could be solved by allowing message to
      // be a function instead...
      form = $(templates.form);
  
      // prompt defaults are more complex than others in that
      // users can override more defaults
      options = mergeDialogOptions('prompt', ['cancel', 'confirm'], ['title', 'callback'], arguments);
  
      if (!options.value) {
        options.value = defaults.value;
      }
  
      if (!options.inputType) {
        options.inputType = defaults.inputType;
      }
  
      // capture the user's show value; we always set this to false before
      // spawning the dialog to give us a chance to attach some handlers to
      // it, but we need to make sure we respect a preference not to show it
      shouldShow = (options.show === undefined) ? defaults.show : options.show;
      // This is required prior to calling the dialog builder below - we need to 
      // add an event handler just before the prompt is shown
      options.show = false;
  
      // Handles the 'cancel' action
      options.buttons.cancel.callback = options.onEscape = function () {
        return options.callback.call(this, null);
      };
  
      // Prompt submitted - extract the prompt value. This requires a bit of work, 
      // given the different input types available.
      options.buttons.confirm.callback = function () {
        var value;
  
        if (options.inputType === 'checkbox') {
          value = input.find('input:checked').map(function () {
            return $(this).val();
          }).get();
        } else if (options.inputType === 'radio') {
          value = input.find('input:checked').val();
        }
        else {
          if (input[0].checkValidity && !input[0].checkValidity()) {
            // prevents button callback from being called
            return false;
          } else {
            if (options.inputType === 'select' && options.multiple === true) {
              value = input.find('option:selected').map(function () {
                return $(this).val();
              }).get();
            }
            else{
              value = input.val();
            }
          }
        }
  
        return options.callback.call(this, value);
      };
  
      // prompt-specific validation
      if (!options.title) {
        throw new Error('prompt requires a title');
      }
  
      if (!$.isFunction(options.callback)) {
        throw new Error('prompt requires a callback');
      }
  
      if (!templates.inputs[options.inputType]) {
        throw new Error('Invalid prompt type');
      }
  
      // create the input based on the supplied type
      input = $(templates.inputs[options.inputType]);
  
      switch (options.inputType) {
        case 'text':
        case 'textarea':
        case 'email':
        case 'password':
          input.val(options.value);
          
          if (options.placeholder) {
            input.attr('placeholder', options.placeholder);
          }
      
          if (options.pattern) {
            input.attr('pattern', options.pattern);
          }
      
          if (options.maxlength) {
            input.attr('maxlength', options.maxlength);
          }
  
          if (options.required) {
            input.prop({ 'required': true });
          }
          
          if (options.rows && !isNaN(parseInt(options.rows))) {
            if(options.inputType === 'textarea'){
              input.attr({ 'rows': options.rows });
            }
          }
  
          break;
  
  
        case 'date':
        case 'time':
        case 'number':
        case 'range':
          input.val(options.value);
          
          if (options.placeholder) {
            input.attr('placeholder', options.placeholder);
          }
      
          if (options.pattern) {
            input.attr('pattern', options.pattern);
          }
  
          if (options.required) {
            input.prop({ 'required': true });
          }
          
          // These input types have extra attributes which affect their input validation.
          // Warning: For most browsers, date inputs are buggy in their implementation of 'step', so 
          // this attribute will have no effect. Therefore, we don't set the attribute for date inputs.
          // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#Setting_maximum_and_minimum_dates
          if (options.inputType !== 'date') {
            if (options.step) {
              if (options.step === 'any' || (!isNaN(options.step) && parseInt(options.step) > 0)) {
                input.attr('step', options.step);
              }
              else {
                throw new Error('"step" must be a valid positive number or the value "any". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-step for more information.');
              }
            }
          }
  
          if(minAndMaxAreValid(options.inputType, options.min, options.max)){
            if(options.min !== undefined){
              input.attr('min', options.min);
            }
            if(options.max !== undefined){
              input.attr('max', options.max);
            }
          }
  
          break;
  
  
        case 'select':
          var groups = {};
          inputOptions = options.inputOptions || [];
  
          if (!$.isArray(inputOptions)) {
            throw new Error('Please pass an array of input options');
          }
  
          if (!inputOptions.length) {
            throw new Error('prompt with "inputType" set to "select" requires at least one option');
          }
  
          // placeholder is not actually a valid attribute for select,
          // but we'll allow it, assuming it might be used for a plugin
          if (options.placeholder) {
            input.attr('placeholder', options.placeholder);
          }
          
          if (options.required) {
            input.prop({ 'required': true });
          }
          
          if (options.multiple) {
            input.prop({ 'multiple': true });
          }
          
          each(inputOptions, function (_, option) {
            // assume the element to attach to is the input...
            var elem = input;
  
            if (option.value === undefined || option.text === undefined) {
              throw new Error('each option needs a "value" property and a "text" property');
            }
  
            // ... but override that element if this option sits in a group
  
            if (option.group) {
              // initialise group if necessary
              if (!groups[option.group]) {
                groups[option.group] = $('<optgroup />').attr('label', option.group);
              }
  
              elem = groups[option.group];
            }
  
            var o = $(templates.option);
            o.attr('value', option.value).text(option.text);
            elem.append(o);
          });
  
          each(groups, function (_, group) {
            input.append(group);
          });
  
          // safe to set a select's value as per a normal input
          input.val(options.value);
  
          break;
  
  
        case 'checkbox':
          var checkboxValues = $.isArray(options.value) ? options.value : [options.value];
          inputOptions = options.inputOptions || [];
  
          if (!inputOptions.length) {
            throw new Error('prompt with "inputType" set to "checkbox" requires at least one option');
          }
  
          // checkboxes have to nest within a containing element, so
          // they break the rules a bit and we end up re-assigning
          // our 'input' element to this container instead
          input = $('<div class="bootbox-checkbox-list"></div>');
  
          each(inputOptions, function (_, option) {
            if (option.value === undefined || option.text === undefined) {
              throw new Error('each option needs a "value" property and a "text" property');
            }
  
            var checkbox = $(templates.inputs[options.inputType]);
  
            checkbox.find('input').attr('value', option.value);
            checkbox.find('label').append('\n' + option.text);
  
            // we've ensured values is an array so we can always iterate over it
            each(checkboxValues, function (_, value) {
              if (value === option.value) {
                checkbox.find('input').prop('checked', true);
              }
            });
  
            input.append(checkbox);
          });
          break;
  
  
        case 'radio':
          // Make sure that value is not an array (only a single radio can ever be checked)
          if (options.value !== undefined && $.isArray(options.value)) {
            throw new Error('prompt with "inputType" set to "radio" requires a single, non-array value for "value"');
          }
  
          inputOptions = options.inputOptions || [];
  
          if (!inputOptions.length) {
            throw new Error('prompt with "inputType" set to "radio" requires at least one option');
          }
  
          // Radiobuttons have to nest within a containing element, so
          // they break the rules a bit and we end up re-assigning
          // our 'input' element to this container instead
          input = $('<div class="bootbox-radiobutton-list"></div>');
  
          // Radiobuttons should always have an initial checked input checked in a "group".
          // If value is undefined or doesn't match an input option, select the first radiobutton
          var checkFirstRadio = true;
  
          each(inputOptions, function (_, option) {
            if (option.value === undefined || option.text === undefined) {
              throw new Error('each option needs a "value" property and a "text" property');
            }
  
            var radio = $(templates.inputs[options.inputType]);
  
            radio.find('input').attr('value', option.value);
            radio.find('label').append('\n' + option.text);
  
            if (options.value !== undefined) {
              if (option.value === options.value) {
                radio.find('input').prop('checked', true);
                checkFirstRadio = false;
              }
            }
  
            input.append(radio);
          });
  
          if (checkFirstRadio) {
            input.find('input[type="radio"]').first().prop('checked', true);
          }
          break;
      }
  
      // now place it in our form
      form.append(input);
  
      form.on('submit', function (e) {
        e.preventDefault();
        // Fix for SammyJS (or similar JS routing library) hijacking the form post.
        e.stopPropagation();
  
        // @TODO can we actually click *the* button object instead?
        // e.g. buttons.confirm.click() or similar
        promptDialog.find('.bootbox-accept').trigger('click');
      });
  
      if ($.trim(options.message) !== '') {
        // Add the form to whatever content the user may have added.
        var message = $(templates.promptMessage).html(options.message);
        form.prepend(message);
        options.message = form;
      }
      else {
        options.message = form;
      }
  
      // Generate the dialog
      promptDialog = exports.dialog(options);
  
      // clear the existing handler focusing the submit button...
      promptDialog.off('shown.bs.modal');
  
      // ...and replace it with one focusing our input, if possible
      promptDialog.on('shown.bs.modal', function () {
        // need the closure here since input isn't
        // an object otherwise
        input.focus();
      });
  
      if (shouldShow === true) {
        promptDialog.modal('show');
      }
  
      return promptDialog;
    };
  
  
    // INTERNAL FUNCTIONS
    // *************************************************************************************************************
  
    // Map a flexible set of arguments into a single returned object
    // If args.length is already one just return it, otherwise
    // use the properties argument to map the unnamed args to
    // object properties.
    // So in the latter case:
    //  mapArguments(["foo", $.noop], ["message", "callback"])
    //  -> { message: "foo", callback: $.noop }
    function mapArguments(args, properties) {
      var argn = args.length;
      var options = {};
  
      if (argn < 1 || argn > 2) {
        throw new Error('Invalid argument length');
      }
  
      if (argn === 2 || typeof args[0] === 'string') {
        options[properties[0]] = args[0];
        options[properties[1]] = args[1];
      } else {
        options = args[0];
      }
  
      return options;
    }
  
  
    //  Merge a set of default dialog options with user supplied arguments
    function mergeArguments(defaults, args, properties) {
      return $.extend(
        // deep merge
        true,
        // ensure the target is an empty, unreferenced object
        {},
        // the base options object for this type of dialog (often just buttons)
        defaults,
        // args could be an object or array; if it's an array properties will
        // map it to a proper options object
        mapArguments(
          args,
          properties
        )
      );
    }
  
  
    //  This entry-level method makes heavy use of composition to take a simple
    //  range of inputs and return valid options suitable for passing to bootbox.dialog
    function mergeDialogOptions(className, labels, properties, args) {
      var locale;
      if(args && args[0]){
        locale = args[0].locale || defaults.locale;
        var swapButtons = args[0].swapButtonOrder || defaults.swapButtonOrder;
  
        if(swapButtons){
          labels = labels.reverse();
        }
      }
  
      //  build up a base set of dialog properties
      var baseOptions = {
        className: 'bootbox-' + className,
        buttons: createLabels(labels, locale)
      };
  
      // Ensure the buttons properties generated, *after* merging
      // with user args are still valid against the supplied labels
      return validateButtons(
        // merge the generated base properties with user supplied arguments
        mergeArguments(
          baseOptions,
          args,
          // if args.length > 1, properties specify how each arg maps to an object key
          properties
        ),
        labels
      );
    }
  
  
    //  Checks each button object to see if key is valid. 
    //  This function will only be called by the alert, confirm, and prompt helpers. 
    function validateButtons(options, buttons) {
      var allowedButtons = {};
      each(buttons, function (key, value) {
        allowedButtons[value] = true;
      });
  
      each(options.buttons, function (key) {
        if (allowedButtons[key] === undefined) {
          throw new Error('button key "' + key + '" is not allowed (options are ' + buttons.join(' ') + ')');
        }
      });
  
      return options;
    }
  
  
  
    //  From a given list of arguments, return a suitable object of button labels.
    //  All this does is normalise the given labels and translate them where possible.
    //  e.g. "ok", "confirm" -> { ok: "OK", cancel: "Annuleren" }
    function createLabels(labels, locale) {
      var buttons = {};
  
      for (var i = 0, j = labels.length; i < j; i++) {
        var argument = labels[i];
        var key = argument.toLowerCase();
        var value = argument.toUpperCase();
  
        buttons[key] = {
          label: getText(value, locale)
        };
      }
  
      return buttons;
    }
  
  
  
    //  Get localized text from a locale. Defaults to 'en' locale if no locale 
    //  provided or a non-registered locale is requested
    function getText(key, locale) {
      var labels = locales[locale];
  
      return labels ? labels[key] : locales.en[key];
    }
  
  
  
    //  Filter and tidy up any user supplied parameters to this dialog.
    //  Also looks for any shorthands used and ensures that the options
    //  which are returned are all normalized properly
    function sanitize(options) {
      var buttons;
      var total;
  
      if (typeof options !== 'object') {
        throw new Error('Please supply an object of options');
      }
  
      if (!options.message) {
        throw new Error('"message" option must not be null or an empty string.');
      }
  
      // make sure any supplied options take precedence over defaults
      options = $.extend({}, defaults, options);
  
      // no buttons is still a valid dialog but it's cleaner to always have
      // a buttons object to iterate over, even if it's empty
      if (!options.buttons) {
        options.buttons = {};
      }
  
      buttons = options.buttons;
  
      total = getKeyLength(buttons);
  
      each(buttons, function (key, button, index) {
        if ($.isFunction(button)) {
          // short form, assume value is our callback. Since button
          // isn't an object it isn't a reference either so re-assign it
          button = buttons[key] = {
            callback: button
          };
        }
  
        // before any further checks make sure by now button is the correct type
        if ($.type(button) !== 'object') {
          throw new Error('button with key "' + key + '" must be an object');
        }
  
        if (!button.label) {
          // the lack of an explicit label means we'll assume the key is good enough
          button.label = key;
        }
  
        if (!button.className) {     
          var isPrimary = false;
          if(options.swapButtonOrder){
            isPrimary = index === 0;
          }
          else{
            isPrimary = index === total-1;
          }
  
          if (total <= 2 && isPrimary) {
            // always add a primary to the main option in a one or two-button dialog
            button.className = 'btn-primary';
          } else {
            // adding both classes allows us to target both BS3 and BS4 without needing to check the version
            button.className = 'btn-secondary btn-default';
          }
        }
      });
  
      return options;
    }
  
  
    //  Returns a count of the properties defined on the object
    function getKeyLength(obj) {
      return Object.keys(obj).length;
    }
  
  
    //  Tiny wrapper function around jQuery.each; just adds index as the third parameter
    function each(collection, iterator) {
      var index = 0;
      $.each(collection, function (key, value) {
        iterator(key, value, index++);
      });
    }
  
  
    //  Handle the invoked dialog callback
    function processCallback(e, dialog, callback) {
      e.stopPropagation();
      e.preventDefault();
  
      // by default we assume a callback will get rid of the dialog,
      // although it is given the opportunity to override this
  
      // so, if the callback can be invoked and it *explicitly returns false*
      // then we'll set a flag to keep the dialog active...
      var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
  
      // ... otherwise we'll bin it
      if (!preserveDialog) {
        dialog.modal('hide');
      }
    }
    
    // Validate `min` and `max` values based on the current `inputType` value
    function minAndMaxAreValid(type, min, max){
      var result = false;
      var minValid = true;
      var maxValid = true;
  
      if (type === 'date') {
        if (min !== undefined && !(minValid = dateIsValid(min))) {
          console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your min value may not be enforced by this browser.');
        }
        else if (max !== undefined && !(maxValid = dateIsValid(max))) {
          console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your max value may not be enforced by this browser.');
        }
      }
      else if (type === 'time') {
        if (min !== undefined && !(minValid = timeIsValid(min))) {
          throw new Error('"min" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');
        }
        else if (max !== undefined && !(maxValid = timeIsValid(max))) {
          throw new Error('"max" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');
        }
      }
      else {
        if (min !== undefined && isNaN(min)) {
          throw new Error('"min" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-min for more information.');
        }
  
        if (max !== undefined && isNaN(max)) {
          throw new Error('"max" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');
        }
      }
      
      if(minValid && maxValid){
        if(max <= min){
          throw new Error('"max" must be greater than "min". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');
        }
        else{
          result = true;
        }
      }
  
      return result;
    }
  
    function timeIsValid(value){
      return /([01][0-9]|2[0-3]):[0-5][0-9]?:[0-5][0-9]/.test(value);
    }
  
    function dateIsValid(value){
      return /(\d{4})-(\d{2})-(\d{2})/.test(value);
    }
  
  
    //  Register the default locale
    exports.addLocale('en', {
      OK: 'OK',
      CANCEL: 'Cancel',
      CONFIRM: 'OK'
    });
  
  
    //  The Bootbox object
    return exports;
  }));